/**
 * 
 */
package stc.skymobi.bean.tlv.encode.encoders;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import stc.skymobi.bean.bytebean.ByteFieldToDesc;
import stc.skymobi.bean.bytebean.codec.BeanCodecUtil;
import stc.skymobi.bean.bytebean.core.ByteFieldCodec;
import stc.skymobi.bean.bytebean.core.ByteFieldDesc;
import stc.skymobi.bean.bytebean.core.EncContextFactory;
import stc.skymobi.bean.bytebean.core.FieldCodecCategory;
import stc.skymobi.bean.bytebean.core.FieldCodecProvider;
import stc.skymobi.bean.tlv.annotation.TLVAttribute;
import stc.skymobi.bean.tlv.encode.TLVEncodeContext;
import stc.skymobi.bean.tlv.encode.TLVEncodeContextFactory;
import stc.skymobi.bean.tlv.encode.TLVEncoder;
import stc.skymobi.bean.tlv.encode.TLVEncoderOfBean;
import stc.skymobi.bean.tlv.meta.TLVCodecUtils;
import stc.skymobi.util.ByteUtils;
import stc.skymobi.util.FieldUtils;


/**
 * @author hp
 * 在原encoder的基础上增加对List类型字段的支持
 */
public class BeanMixedTLVEncoder implements TLVEncoderOfBean {

    private static final Logger LOG = 
    	LoggerFactory.getLogger(BeanMixedTLVEncoder.class);
    
    private	TLVEncodeContextFactory	tlvEncodeContextFactory;
	private	EncContextFactory	byteEncContextFactory;
	private	FieldCodecProvider	byteCodecProvider;
    
	private final BeanCodecUtil util = new BeanCodecUtil(new ByteFieldToDesc());
	
	/**
	 * @return the tlvEncodeContextFactory
	 */
	public TLVEncodeContextFactory getTlvEncodeContextFactory() {
		return tlvEncodeContextFactory;
	}

	/**
	 * @param tlvEncodeContextFactory the tlvEncodeContextFactory to set
	 */
	public void setTlvEncodeContextFactory(
			TLVEncodeContextFactory tlvEncodeContextFactory) {
		this.tlvEncodeContextFactory = tlvEncodeContextFactory;
	}

	/**
	 * @return the byteEncContextFactory
	 */
	public EncContextFactory getByteEncContextFactory() {
		return byteEncContextFactory;
	}

	/**
	 * @param byteEncContextFactory the byteEncContextFactory to set
	 */
	public void setByteEncContextFactory(EncContextFactory byteEncContextFactory) {
		this.byteEncContextFactory = byteEncContextFactory;
	}

	/**
	 * @return the byteCodecProvider
	 */
	public FieldCodecProvider getByteCodecProvider() {
		return byteCodecProvider;
	}

	/**
	 * @param byteCodecProvider the byteCodecProvider to set
	 */
	public void setByteCodecProvider(FieldCodecProvider byteCodecProvider) {
		this.byteCodecProvider = byteCodecProvider;
	}

	/* (non-Javadoc)
	 * @see com.skymobi.bean.tlv.encode.encoders.TLVEncoderOfBean#getEncodeContextFactory()
	 */
	public TLVEncodeContextFactory getEncodeContextFactory() {
		return tlvEncodeContextFactory;
	}

	/* (non-Javadoc)
	 * @see com.skymobi.bean.tlv.encode.encoders.TLVEncoderOfBean#encode(java.lang.Object, com.skymobi.bean.tlv.encode.TLVEncodeContext)
	 */
	public List<byte[]> encode(final Object tlvBean, final TLVEncodeContext ctx) {
        if ( null == tlvBean ) {
            throw new RuntimeException("null == tlvBean.");
        }

        final List<byte[]> ret = new ArrayList<byte[]>();
        
		encodeByteFields(tlvBean, ret);
        
        encodeTLVFields(tlvBean, ctx, ret);
        
        return ret;
	}

	/**
	 * @param tlvBean
	 * @param ctx
	 * @param ret
	 */
	@SuppressWarnings("unchecked")
	private void encodeTLVFields(final Object tlvBean,
			final TLVEncodeContext ctx, final List<byte[]> ret) {
		final Field[] fields = TLVCodecUtils.getTLVFieldsOf(tlvBean.getClass());
        
        for ( Field field : fields ) {
            final TLVAttribute param  = field.getAnnotation(TLVAttribute.class);
            
            if ( null == param ) {
                //  not TLVAttribute
                continue;
            }
            
            field.setAccessible(true);
            Object src = null;
            try {
                src = field.get(tlvBean);
            } catch (IllegalArgumentException e) {
            	LOG.error("transform:", e);
            } catch (IllegalAccessException e) {
            	LOG.error("transform:", e);
            }
            if ( null == src ) {
                continue;
            }
            
            Class<?>	type = param.type();
            if ( TLVAttribute.class.equals(type)) {
            	type = src.getClass();
            }
            
            if ( List.class.isAssignableFrom(type)) {
            	final Class<?> componentType = FieldUtils.getComponentClass(field);
                final TLVEncoder encoder = ctx.getEncoderRepository().getEncoderOf(componentType);
                if ( null == encoder ) {
                	LOG.error("field[" + field + "]/"+componentType.getSimpleName()+ " can not found encoder, ignore");
                    continue;
                }
                final List<Object> list = (List<Object>)src;
                for ( Object component : list ) {
                    final List<byte[]> dest = encoder.encode(component, 
                    		tlvEncodeContextFactory.createEncodeContext(componentType, field) );
                    //	tag
                    ret.add( ctx.getNumberCodec().int2Bytes(param.tag(), 4) );
                    
                    //	len
                    ret.add( ctx.getNumberCodec().int2Bytes(ByteUtils.totalByteSizeOf(dest), 4) );
                    
                    ret.addAll(dest);
                }
            }
            else {
	            final TLVEncoder encoder = ctx.getEncoderRepository().getEncoderOf(type);
	            if ( null == encoder ) {
	            	LOG.error("field[" + field + "] can not found encoder, ignore");
	                continue;
	            }
	            final List<byte[]> dest = encoder.encode(src, 
	            		tlvEncodeContextFactory.createEncodeContext(type, field) );
	
	            //	tag
	            ret.add( ctx.getNumberCodec().int2Bytes(param.tag(), 4) );
	            
	            //	len
	            ret.add( ctx.getNumberCodec().int2Bytes(ByteUtils.totalByteSizeOf(dest), 4) );
	            
	            ret.addAll(dest);
            }
        }
	}

	/**
	 * @param tlvBean
	 * @param ret
	 */
	private void encodeByteFields(final Object tlvBean, final List<byte[]> ret) {
		final List<ByteFieldDesc> desces = util.getFieldDesces(tlvBean.getClass());
		if (!desces.isEmpty()) {
			// include ByteField annotation
			final ByteFieldCodec anyCodec = byteCodecProvider
					.getCodecOf(FieldCodecCategory.ANY);

			for (ByteFieldDesc desc : desces) {

				final Field field = desc.getField();
				final Class<?> fieldClass = field.getType();

	            field.setAccessible(true);
	            Object fieldValue = null;
	            
	            try {
	                fieldValue = field.get(tlvBean);
	            } catch (IllegalArgumentException e) {
	                LOG.error( "BeanMixedTLVEncoder:", e);
	            } catch (IllegalAccessException e) {
	                LOG.error( "BeanMixedTLVEncoder:", e);
	            }
	            
	            ret.add( 
            		anyCodec.encode(
                		byteEncContextFactory.createEncContext(
                				fieldValue, fieldClass, desc) ) );
			}
		}
	}

}
